{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "uh7LaSzfwcj2",
    "outputId": "08573a01-e064-4c84-8624-440ffb2a2d6f"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1.13.1+cu116\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "import torch\n",
    "\n",
    "os.environ['TORCH'] = torch.__version__\n",
    "print(torch.__version__)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "vit8xKCiXAue"
   },
   "source": [
    "# Link Prediction on MovieLens\n",
    "\n",
    "这个笔记展示了如何加载一组*.csv文件作为输入，并从中构建异构图。然后，我们将使用此数据集作为输入到异构图模型中，并将其用于链接预测任务。\n",
    "\n",
    "我们将使用GroupLens研究小组收集的MovieLens数据集。这个数据集描述了MovieLens的评分和标记活动。该数据集包含来自600多个用户的9000多部电影的约10万个评分。我们将使用该数据集生成两种节点类型，分别保存电影和用户的数据，以及一种连接用户和电影的边缘类型，表示用户是否对特定电影进行了评级的关系。\n",
    "\n",
    "然后，链接预测任务尝试预测缺失的评分，例如，可以用于向用户推荐新电影。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "N_pshZh8Y3dw"
   },
   "source": [
    "## 1.Heterogeneous Graph Creation"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "ciYr7NWEv5_o",
    "outputId": "5bbe4a86-5d5b-4e42-d876-c7b58d8e030b"
   },
   "outputs": [],
   "source": [
    "movies_path = './data/ml-latest-small/movies.csv'\n",
    "ratings_path = './data/ml-latest-small/ratings.csv'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "B89RD_evY7uu"
   },
   "source": [
    "在创建异构图之前，让我们先来看看数据。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "D6ixkcCOwCDi",
    "outputId": "4bc902aa-4707-48ef-c5f0-65a8d0f0faf3"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "movies.csv:\n",
      "===========\n",
      "   movieId                                       genres\n",
      "0        1  Adventure|Animation|Children|Comedy|Fantasy\n",
      "1        2                   Adventure|Children|Fantasy\n",
      "2        3                               Comedy|Romance\n",
      "3        4                         Comedy|Drama|Romance\n",
      "4        5                                       Comedy\n",
      "5        6                        Action|Crime|Thriller\n",
      "6        7                               Comedy|Romance\n",
      "7        8                           Adventure|Children\n",
      "8        9                                       Action\n",
      "9       10                    Action|Adventure|Thriller\n",
      "\n",
      "ratings.csv:\n",
      "============\n",
      "   userId  movieId\n",
      "0       1        1\n",
      "1       1        3\n",
      "2       1        6\n",
      "3       1       47\n",
      "4       1       50\n",
      "5       1       70\n",
      "6       1      101\n",
      "7       1      110\n",
      "8       1      151\n",
      "9       1      157\n"
     ]
    }
   ],
   "source": [
    "import pandas as pd\n",
    "\n",
    "print('movies.csv:')\n",
    "print('===========')\n",
    "print(pd.read_csv(movies_path)[[\"movieId\", \"genres\"]].head(10))\n",
    "print()\n",
    "print('ratings.csv:')\n",
    "print('============')\n",
    "print(pd.read_csv(ratings_path)[[\"userId\", \"movieId\"]].head(10))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "LgXipuWTZK39"
   },
   "source": [
    "我们可以看到`movies.csv`文件提供了两个有用的列：`movieId`为每部电影分配唯一的标识符，而`genres`列表示电影的体裁。\n",
    "\n",
    "我们可以利用这些定义一个可以被机器学习模型轻松解释的特征表示。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "dd_BJMNcwJ7k",
    "outputId": "d9c2d6b4-d968-4226-e2d6-b643683931f2"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "         Action  Adventure  Drama  Horror\n",
      "movieId                                  \n",
      "1             0          1      0       0\n",
      "2             0          1      0       0\n",
      "3             0          0      0       0\n",
      "4             0          0      1       0\n",
      "5             0          0      0       0\n"
     ]
    }
   ],
   "source": [
    "# 将整个电影数据加载到内存中\n",
    "movies_df = pd.read_csv(movies_path, index_col='movieId')\n",
    "\n",
    "# 体裁转换为指示变量（one-hot编码）\n",
    "genres = movies_df['genres'].str.get_dummies('|')\n",
    "print(genres[[\"Action\", \"Adventure\", \"Drama\", \"Horror\"]].head())\n",
    "\n",
    "# 使用genres作为电影输入特征\n",
    "movie_feat = torch.from_numpy(genres.values).to(torch.float)\n",
    "assert movie_feat.size() == (9742, 20)  # 一共20 genres"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "KLaDBVP4plsS"
   },
   "source": [
    "The `ratings.csv` data connects users (as given by `userId`) and movies (as given by `movieId`).\n",
    "Due to simplicity, we do not make use of the additional `timestamp` and `rating` information.\n",
    "Here, we first read the `*.csv` file from disk, and create a mapping that maps entry IDs to a consecutive value in the range `{ 0, ..., num_rows - 1 }`.\n",
    "This is needed as we want our final data representation to be as compact as possible, *e.g.*, the representation of a movie in the first row should be accessible via `x[0]`.\n",
    "\n",
    "Afterwards, we obtain the final `edge_index` representation of shape `[2, num_ratings]` from `ratings.csv` by merging mapped user and movie indices with the raw indices given by the original data frame."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "JMGYv83WzSRr",
    "outputId": "35e859ca-75cb-407e-9a7c-b0d4b7b76fbb"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Mapping of user IDs to consecutive values:\n",
      "==========================================\n",
      "   userId  mappedID\n",
      "0       1         0\n",
      "1       2         1\n",
      "2       3         2\n",
      "3       4         3\n",
      "4       5         4\n",
      "\n",
      "Mapping of movie IDs to consecutive values:\n",
      "===========================================\n",
      "   movieId  mappedID\n",
      "0        1         0\n",
      "1        3         1\n",
      "2        6         2\n",
      "3       47         3\n",
      "4       50         4\n",
      "\n",
      "Final edge indices pointing from users to movies:\n",
      "=================================================\n",
      "tensor([[   0,    0,    0,  ...,  609,  609,  609],\n",
      "        [   0,    1,    2,  ..., 3121, 1392, 2873]])\n",
      "torch.Size([2, 100836])\n"
     ]
    }
   ],
   "source": [
    "# 将ratings data frame加载进内存\n",
    "ratings_df = pd.read_csv(ratings_path)\n",
    "\n",
    "# 创建从唯一用户索引到范围[0, num_user_nodes)的映射\n",
    "unique_user_id = ratings_df['userId'].unique()\n",
    "unique_user_id = pd.DataFrame(data={\n",
    "    'userId': unique_user_id,\n",
    "    'mappedID': pd.RangeIndex(len(unique_user_id)),\n",
    "})\n",
    "print(\"Mapping of user IDs to consecutive values:\")\n",
    "print(\"==========================================\")\n",
    "print(unique_user_id.head())\n",
    "\n",
    "print()\n",
    "\n",
    "# 创建从唯一电影索引到范围[0, num_movie_nodes)的映射\n",
    "unique_movie_id = ratings_df['movieId'].unique()\n",
    "unique_movie_id = pd.DataFrame(data={\n",
    "    'movieId': unique_movie_id,\n",
    "    'mappedID': pd.RangeIndex(len(unique_movie_id)),\n",
    "})\n",
    "print(\"Mapping of movie IDs to consecutive values:\")\n",
    "print(\"===========================================\")\n",
    "print(unique_movie_id.head())\n",
    "\n",
    "# 获取user和movie的原始ID和映射ID\n",
    "ratings_user_id = pd.merge(ratings_df['userId'], unique_user_id,\n",
    "                            left_on='userId', right_on='userId', how='left')\n",
    "ratings_user_id = torch.from_numpy(ratings_user_id['mappedID'].values)\n",
    "\n",
    "ratings_movie_id = pd.merge(ratings_df['movieId'], unique_movie_id,\n",
    "                            left_on='movieId', right_on='movieId', how='left')\n",
    "ratings_movie_id = torch.from_numpy(ratings_movie_id['mappedID'].values)\n",
    "\n",
    "# 构造`edge_index` in COO format\n",
    "edge_index_user_to_movie = torch.stack([ratings_user_id, ratings_movie_id], dim=0)\n",
    "assert edge_index_user_to_movie.size() == (2, 100836)\n",
    "\n",
    "print()\n",
    "print(\"Final edge indices pointing from users to movies:\")\n",
    "print(\"=================================================\")\n",
    "print(edge_index_user_to_movie)\n",
    "print(edge_index_user_to_movie.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "9w9fCnjvqmd2"
   },
   "source": [
    "这样，我们就可以初始化我们的`HeteroData`对象，并将必要的信息传递给它。\n",
    "\n",
    "请注意，我们还将`node_id`向量传递给每个节点类型，以便从采样的子图中重建原始节点索引。\n",
    "\n",
    "我们还负责向`HeteroData`对象添加反向边。这允许我们的GNN模型使用边的两个方向进行信息传递。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "I_63--974srt",
    "outputId": "32fe707d-f9ac-4dca-8c7b-52bbb8ddfa07"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "HeteroData(\n",
      "  user={ node_id=[610] },\n",
      "  movie={\n",
      "    node_id=[9742],\n",
      "    x=[9742, 20],\n",
      "  },\n",
      "  (user, rates, movie)={ edge_index=[2, 100836] },\n",
      "  (movie, rev_rates, user)={ edge_index=[2, 100836] }\n",
      ")\n"
     ]
    }
   ],
   "source": [
    "from torch_geometric.data import HeteroData\n",
    "import torch_geometric.transforms as T\n",
    "\n",
    "data = HeteroData()\n",
    "\n",
    "# 保存节点索引\n",
    "data[\"user\"].node_id = torch.arange(len(unique_user_id))\n",
    "data[\"movie\"].node_id = torch.arange(len(movies_df))\n",
    "\n",
    "# 添加节点特征和边索引\n",
    "data[\"movie\"].x = movie_feat\n",
    "data[\"user\", \"rates\", \"movie\"].edge_index = edge_index_user_to_movie\n",
    "\n",
    "# add the reverse edges from movies to users\n",
    "# 以便使GNN能够在两个方向上传递信息\n",
    "# We can leverage the `T.ToUndirected()` transform for this from PyG:\n",
    "data = T.ToUndirected()(data)\n",
    "\n",
    "print(data)\n",
    "\n",
    "assert data.node_types == [\"user\", \"movie\"]\n",
    "assert data.edge_types == [(\"user\", \"rates\", \"movie\"),\n",
    "                           (\"movie\", \"rev_rates\", \"user\")]\n",
    "\n",
    "assert data[\"user\"].num_nodes == 610\n",
    "assert data[\"user\"].num_features == 0\n",
    "assert data[\"movie\"].num_nodes == 9742\n",
    "assert data[\"movie\"].num_features == 20\n",
    "\n",
    "assert data[\"user\", \"rates\", \"movie\"].num_edges == 100836\n",
    "assert data[\"movie\", \"rev_rates\", \"user\"].num_edges == 100836"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "2QGdkLAurBq9"
   },
   "source": [
    "## 2.Defining Edge-level Training Splits\n",
    "\n",
    "Since our data is now ready-to-be-used, we can split the ratings of users into training, validation, and test splits.\n",
    "This is needed in order to ensure that we leak no information about edges used during evaluation into the training phase.\n",
    "For this, we make use of the [`transforms.RandomLinkSplit`](https://pytorch-geometric.readthedocs.io/en/latest/modules/transforms.html#torch_geometric.transforms.RandomLinkSplit) transformation from PyG.\n",
    "This transforms randomly divides the edges in the `(\"user\", \"rates\", \"movie\")` into training, validation and test edges.\n",
    "The `disjoint_train_ratio` parameter further separates edges in the training split into edges used for message passing (`edge_index`) and edges used for supervision (`edge_label_index`).\n",
    "Note that we also need to specify the reverse edge type `(\"movie\", \"rev_rates\", \"user\")`.\n",
    "This allows the `RandomLinkSplit` transform to drop reverse edges accordingly to not leak any information into the training phase."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "id": "rwgNwoa26Eja"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Training data:\n",
      "==============\n",
      "HeteroData(\n",
      "  user={ node_id=[610] },\n",
      "  movie={\n",
      "    node_id=[9742],\n",
      "    x=[9742, 20],\n",
      "  },\n",
      "  (user, rates, movie)={\n",
      "    edge_index=[2, 56469],\n",
      "    edge_label=[24201],\n",
      "    edge_label_index=[2, 24201],\n",
      "  },\n",
      "  (movie, rev_rates, user)={ edge_index=[2, 56469] }\n",
      ")\n",
      "\n",
      "Validation data:\n",
      "================\n",
      "HeteroData(\n",
      "  user={ node_id=[610] },\n",
      "  movie={\n",
      "    node_id=[9742],\n",
      "    x=[9742, 20],\n",
      "  },\n",
      "  (user, rates, movie)={\n",
      "    edge_index=[2, 80670],\n",
      "    edge_label=[30249],\n",
      "    edge_label_index=[2, 30249],\n",
      "  },\n",
      "  (movie, rev_rates, user)={ edge_index=[2, 80670] }\n",
      ")\n",
      "\n",
      "Testing data:\n",
      "================\n",
      "HeteroData(\n",
      "  user={ node_id=[610] },\n",
      "  movie={\n",
      "    node_id=[9742],\n",
      "    x=[9742, 20],\n",
      "  },\n",
      "  (user, rates, movie)={\n",
      "    edge_index=[2, 90753],\n",
      "    edge_label=[30249],\n",
      "    edge_label_index=[2, 30249],\n",
      "  },\n",
      "  (movie, rev_rates, user)={ edge_index=[2, 90753] }\n",
      ")\n",
      "tensor([1., 1., 1.,  ..., 1., 1., 1.])\n",
      "tensor([[ 508,  246,  178,  ...,  199,  598,  473],\n",
      "        [1993,  240,  469,  ..., 1134, 8444, 3271]])\n"
     ]
    }
   ],
   "source": [
    "# 训练：80%，验证：10%，测试：10%\n",
    "# 训练阶段：70%的边用于消息传递；30%的边用于监督\n",
    "# 负采样边比例为2\n",
    "transform = T.RandomLinkSplit(\n",
    "    num_val=0.1,\n",
    "    num_test=0.1,\n",
    "    disjoint_train_ratio=0.3,\n",
    "    neg_sampling_ratio=2.0,\n",
    "    add_negative_train_samples=False,        # 是否为链接预测添加负训练样本。如果模型已经执行负采样，则该选项应设置为False。否则，除非再次执行负采样，否则在训练迭代中添加的负样本将是相同的。\n",
    "    edge_types=(\"user\", \"rates\", \"movie\"),\n",
    "    rev_edge_types=(\"movie\", \"rev_rates\", \"user\"), \n",
    ")\n",
    "\n",
    "train_data, val_data, test_data = transform(data)\n",
    "print(\"Training data:\")\n",
    "print(\"==============\")\n",
    "print(train_data)\n",
    "print()\n",
    "print(\"Validation data:\")\n",
    "print(\"================\")\n",
    "print(val_data)\n",
    "print()\n",
    "print(\"Testing data:\")\n",
    "print(\"================\")\n",
    "print(test_data)\n",
    "\n",
    "print(train_data[\"user\", \"rates\", \"movie\"].edge_label)\n",
    "print(train_data[\"user\", \"rates\", \"movie\"].edge_label_index)\n",
    "\n",
    "assert train_data[\"user\", \"rates\", \"movie\"].num_edges == 56469\n",
    "assert train_data[\"user\", \"rates\", \"movie\"].edge_label_index.size(1) == 24201\n",
    "assert train_data[\"movie\", \"rev_rates\", \"user\"].num_edges == 56469\n",
    "\n",
    "# 没有负采样边（标签都为1）\n",
    "assert train_data[\"user\", \"rates\", \"movie\"].edge_label.min() == 1\n",
    "assert train_data[\"user\", \"rates\", \"movie\"].edge_label.max() == 1\n",
    "\n",
    "assert val_data[\"user\", \"rates\", \"movie\"].num_edges == 80670\n",
    "assert val_data[\"user\", \"rates\", \"movie\"].edge_label_index.size(1) == 30249\n",
    "assert val_data[\"movie\", \"rev_rates\", \"user\"].num_edges == 80670\n",
    "\n",
    "# 负采样边比例为2\n",
    "assert val_data[\"user\", \"rates\", \"movie\"].edge_label.long().bincount().tolist() == [20166, 10083]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "训练边:验证边:测试边=0.8:0.1:0.1，总共100836\n",
    "\n",
    "训练边中：消息传递:监督=0.7:0.3，训练边一共80670，其中消息边为80670 * 0.7=56469(edge_index)；监督边24201\n",
    "\n",
    "验证边和测试边正样本（标签为1）各自为100836 * 0.1 ≈10083，由于有负采样，所以edge_label都为10083 * 3 =30249 "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "prKLwq6RsYoh"
   },
   "source": [
    "## 3.Defining Mini-batch Loaders\n",
    "\n",
    "We are now ready to create a mini-batch loader that will generate subgraphs that can be used as input into our GNN.\n",
    "While this step is not strictly necessary for small-scale graphs, it is absolutely necessary to apply GNNs on larger graphs that do not fit onto GPU memory otherwise.\n",
    "Here, we make use of the [`loader.LinkNeighborLoader`](https://pytorch-geometric.readthedocs.io/en/latest/modules/loader.html#torch_geometric.loader.LinkNeighborLoader) which samples multiple hops from both ends of a link and creates a subgraph from it.\n",
    "Here, `edge_label_index` serves as the \"seed links\" to start sampling from."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "Ogh615ka9I2c",
    "outputId": "e4b6d4d3-f59b-4f8a-9b78-a2536ed08516"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Sampled mini-batch:\n",
      "===================\n",
      "HeteroData(\n",
      "  user={\n",
      "    node_id=[606],\n",
      "    n_id=[606],\n",
      "  },\n",
      "  movie={\n",
      "    node_id=[2687],\n",
      "    x=[2687, 20],\n",
      "    n_id=[2687],\n",
      "  },\n",
      "  (user, rates, movie)={\n",
      "    edge_index=[2, 16881],\n",
      "    edge_label=[384],\n",
      "    edge_label_index=[2, 384],\n",
      "    e_id=[16881],\n",
      "    input_id=[128],\n",
      "  },\n",
      "  (movie, rev_rates, user)={\n",
      "    edge_index=[2, 7676],\n",
      "    e_id=[7676],\n",
      "  }\n",
      ")\n"
     ]
    }
   ],
   "source": [
    "from torch_geometric.loader import LinkNeighborLoader\n",
    "\n",
    "# 种子边\n",
    "edge_label_index = train_data[\"user\", \"rates\", \"movie\"].edge_label_index\n",
    "edge_label = train_data[\"user\", \"rates\", \"movie\"].edge_label\n",
    "\n",
    "# 1-hop，采样20个邻居；2-hop，采样10个邻居\n",
    "# 训练阶段：负采样边:正采样边=2:1\n",
    "train_loader = LinkNeighborLoader(\n",
    "    data=train_data,\n",
    "    num_neighbors=[20, 10],\n",
    "    neg_sampling_ratio=2.0,\n",
    "    edge_label_index=((\"user\", \"rates\", \"movie\"), edge_label_index),\n",
    "    edge_label=edge_label,\n",
    "    batch_size=128,\n",
    "    shuffle=True,\n",
    ")\n",
    "\n",
    "# 一个采样数据\n",
    "sampled_data = next(iter(train_loader))\n",
    "\n",
    "print(\"Sampled mini-batch:\")\n",
    "print(\"===================\")\n",
    "print(sampled_data)\n",
    "\n",
    "assert sampled_data[\"user\", \"rates\", \"movie\"].edge_label_index.size(1) == 3 * 128\n",
    "assert sampled_data[\"user\", \"rates\", \"movie\"].edge_label.min() == 0\n",
    "assert sampled_data[\"user\", \"rates\", \"movie\"].edge_label.max() == 1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{('user',\n",
       "  'rates',\n",
       "  'movie'): tensor([[   0,    0,    0,  ...,  609,  609,  609],\n",
       "         [   0,    1,    2,  ..., 3121, 1392, 2873]]),\n",
       " ('movie',\n",
       "  'rev_rates',\n",
       "  'user'): tensor([[   0,    1,    2,  ..., 3121, 1392, 2873],\n",
       "         [   0,    0,    0,  ...,  609,  609,  609]])}"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 边类型\n",
    "data.edge_index_dict"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "uj7biOtatAmG"
   },
   "source": [
    "## 4.Creating a Heterogeneous Link-level GNN\n",
    "\n",
    "We are now ready to create our heterogeneous GNN.\n",
    "The GNN is responsible for learning enriched node representations from the surrounding subgraphs, which can be then used to derive edge-level predictions.\n",
    "For defining our heterogenous GNN, we make use of [`nn.SAGEConv`](https://pytorch-geometric.readthedocs.io/en/latest/modules/nn.html#torch_geometric.nn.conv.SAGEConv) and the [`nn.to_hetero()`](https://pytorch-geometric.readthedocs.io/en/latest/modules/nn.html#torch_geometric.nn.to_hetero_transformer.to_hetero) function, which transforms a GNN defined on homogeneous graphs to be applied on heterogeneous ones.\n",
    "\n",
    "In addition, we define a final link-level classifier, which simply takes both node embeddings of the link we are trying to predict, and applies a dot-product on them.\n",
    "\n",
    "As users do not have any node-level information, we choose to learn their features jointly via a `torch.nn.Embedding` layer. In order to improve the expressiveness of movie features, we do the same for movie nodes, and simply add their shallow embeddings to the pre-defined genre features."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "ebsFf-Pr_4LF",
    "outputId": "5756285f-592d-4783-a31d-1bbccf8fd454"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Model(\n",
      "  (movie_lin): Linear(in_features=20, out_features=64, bias=True)\n",
      "  (user_emb): Embedding(610, 64)\n",
      "  (movie_emb): Embedding(9742, 64)\n",
      "  (gnn): GraphModule(\n",
      "    (conv1): ModuleDict(\n",
      "      (user__rates__movie): SAGEConv(64, 64, aggr=mean)\n",
      "      (movie__rev_rates__user): SAGEConv(64, 64, aggr=mean)\n",
      "    )\n",
      "    (conv2): ModuleDict(\n",
      "      (user__rates__movie): SAGEConv(64, 64, aggr=mean)\n",
      "      (movie__rev_rates__user): SAGEConv(64, 64, aggr=mean)\n",
      "    )\n",
      "  )\n",
      "  (classifier): Classifier()\n",
      ")\n"
     ]
    }
   ],
   "source": [
    "from torch_geometric.nn import SAGEConv, to_hetero\n",
    "import torch.nn.functional as F\n",
    "from torch import Tensor\n",
    "\n",
    "class GNN(torch.nn.Module):\n",
    "    def __init__(self, hidden_channels):\n",
    "        super().__init__()\n",
    "        self.conv1 = SAGEConv(hidden_channels, hidden_channels)\n",
    "        self.conv2 = SAGEConv(hidden_channels, hidden_channels)\n",
    "    def forward(self, x: Tensor, edge_index: Tensor) -> Tensor:\n",
    "        x = F.relu(self.conv1(x, edge_index))\n",
    "        x = self.conv2(x, edge_index)\n",
    "        return x\n",
    "# Our final classifier applies the dot-product between source and destination\n",
    "# node embeddings to derive edge-level predictions:\n",
    "class Classifier(torch.nn.Module):\n",
    "    def forward(self, x_user: Tensor, x_movie: Tensor, edge_label_index: Tensor) -> Tensor:\n",
    "        # 将节点嵌入转换为边表示：\n",
    "        edge_feat_user = x_user[edge_label_index[0]]\n",
    "        edge_feat_movie = x_movie[edge_label_index[1]]\n",
    "        # Apply dot-product to get a prediction per supervision edge:\n",
    "        return (edge_feat_user * edge_feat_movie).sum(dim=-1)\n",
    "\n",
    "class Model(torch.nn.Module):\n",
    "    def __init__(self, hidden_channels):\n",
    "        super().__init__()\n",
    "        # Since the dataset does not come with rich features, we also learn two\n",
    "        # embedding matrices for users and movies:\n",
    "        self.movie_lin = torch.nn.Linear(20, hidden_channels)\n",
    "        self.user_emb = torch.nn.Embedding(data[\"user\"].num_nodes, hidden_channels)\n",
    "        self.movie_emb = torch.nn.Embedding(data[\"movie\"].num_nodes, hidden_channels)\n",
    "        \n",
    "        # Instantiate homogeneous GNN:\n",
    "        self.gnn = GNN(hidden_channels)\n",
    "        # Convert GNN model into a heterogeneous variant:\n",
    "        self.gnn = to_hetero(self.gnn, metadata=data.metadata())     \n",
    "        self.classifier = Classifier()\n",
    "    def forward(self, data: HeteroData) -> Tensor:\n",
    "        x_dict = {\n",
    "          \"user\": self.user_emb(data[\"user\"].node_id),\n",
    "          \"movie\": self.movie_lin(data[\"movie\"].x) + self.movie_emb(data[\"movie\"].node_id),\n",
    "        } \n",
    "        # `x_dict` holds feature matrices of all node types\n",
    "        # `edge_index_dict` holds all edge indices of all edge types\n",
    "        \n",
    "        x_dict = self.gnn(x_dict, data.edge_index_dict)\n",
    "        \n",
    "        # print(x_dict[\"user\"].shape)\n",
    "        # print(x_dict[\"movie\"].shape)\n",
    "        \n",
    "        pred = self.classifier(\n",
    "            x_dict[\"user\"],\n",
    "            x_dict[\"movie\"],\n",
    "            data[\"user\", \"rates\", \"movie\"].edge_label_index,\n",
    "        )\n",
    "        return pred\n",
    "        \n",
    "model = Model(hidden_channels=64)\n",
    "print(model)"
   ]
  },
  {
   "attachments": {
    "487dc412-7b5a-4512-bba8-8c667a3c834f.png": {
     "image/png": "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"
    }
   },
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![7d296400cb77cddc6625700cfef4c1d.png](attachment:487dc412-7b5a-4512-bba8-8c667a3c834f.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "ebsFf-Pr_4LF",
    "outputId": "5756285f-592d-4783-a31d-1bbccf8fd454"
   },
   "source": [
    "## 5.Training a Heterogeneous Link-level GNN\n",
    "\n",
    "Training our GNN is then similar to training any PyTorch model.\n",
    "We move the model to the desired device, and initialize an optimizer that takes care of adjusting model parameters via stochastic gradient descent.\n",
    "\n",
    "The training loop then iterates over our mini-batches, applies the forward computation of the model, computes the loss from ground-truth labels and obtained predictions (here we make use of binary cross entropy), and adjusts model parameters via back-propagation and stochastic gradient descent."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "ebsFf-Pr_4LF",
    "outputId": "5756285f-592d-4783-a31d-1bbccf8fd454"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Device: 'cuda'\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|████████████████████████████████████████████████████████████████████████████████| 190/190 [00:11<00:00, 17.11it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch: 001, Loss: 0.4483\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|████████████████████████████████████████████████████████████████████████████████| 190/190 [00:10<00:00, 18.11it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch: 002, Loss: 0.3516\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|████████████████████████████████████████████████████████████████████████████████| 190/190 [00:10<00:00, 18.70it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch: 003, Loss: 0.3320\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|████████████████████████████████████████████████████████████████████████████████| 190/190 [00:10<00:00, 18.95it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch: 004, Loss: 0.3127\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|████████████████████████████████████████████████████████████████████████████████| 190/190 [00:10<00:00, 18.16it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch: 005, Loss: 0.2988\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "import tqdm\n",
    "import torch.nn.functional as F\n",
    "device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n",
    "print(f\"Device: '{device}'\")\n",
    "model = model.to(device)\n",
    "optimizer = torch.optim.Adam(model.parameters(), lr=0.001)\n",
    "for epoch in range(1, 6):\n",
    "    total_loss = total_examples = 0\n",
    "    for sampled_data in tqdm.tqdm(train_loader):\n",
    "        optimizer.zero_grad()\n",
    "        sampled_data.to(device)\n",
    "        pred = model(sampled_data)\n",
    "        ground_truth = sampled_data[\"user\", \"rates\", \"movie\"].edge_label\n",
    "        loss = F.binary_cross_entropy_with_logits(pred, ground_truth)\n",
    "        loss.backward()\n",
    "        optimizer.step()\n",
    "        total_loss += float(loss) * pred.numel()\n",
    "        total_examples += pred.numel()\n",
    "    print(f\"Epoch: {epoch:03d}, Loss: {total_loss / total_examples:.4f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "Yq-I2xaYueF0"
   },
   "source": [
    "## 6.Evaluating a Heterogeneous Link-level GNN\n",
    "\n",
    "After training, we evaluate our model on useen data coming from the validation set.\n",
    "For this, we define a new `LinkNeighborLoader` (which now iterates over the edges in the validation set), obtain the predictions on validation edges by running the model, and finally evaluate the performance of the model by computing the AUC score over the set of predictions and their corresponding ground-truth edges (including both positive and negative edges)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "ZIrRn9YoNllj",
    "outputId": "174894fc-fc38-4500-a022-f8232c5015b2"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Sampled mini-batch:\n",
      "===================\n",
      "HeteroData(\n",
      "  user={\n",
      "    node_id=[607],\n",
      "    n_id=[607],\n",
      "  },\n",
      "  movie={\n",
      "    node_id=[2663],\n",
      "    x=[2663, 20],\n",
      "    n_id=[2663],\n",
      "  },\n",
      "  (user, rates, movie)={\n",
      "    edge_index=[2, 19034],\n",
      "    edge_label=[384],\n",
      "    edge_label_index=[2, 384],\n",
      "    e_id=[19034],\n",
      "    input_id=[384],\n",
      "  },\n",
      "  (movie, rev_rates, user)={\n",
      "    edge_index=[2, 7590],\n",
      "    e_id=[7590],\n",
      "  }\n",
      ")\n"
     ]
    }
   ],
   "source": [
    "# Define the validation seed edges:\n",
    "edge_label_index = val_data[\"user\", \"rates\", \"movie\"].edge_label_index\n",
    "edge_label = val_data[\"user\", \"rates\", \"movie\"].edge_label\n",
    "\n",
    "val_loader = LinkNeighborLoader(\n",
    "    data=val_data,\n",
    "    num_neighbors=[20, 10],\n",
    "    edge_label_index=((\"user\", \"rates\", \"movie\"), edge_label_index),\n",
    "    edge_label=edge_label,\n",
    "    batch_size=3 * 128,\n",
    "    shuffle=False,\n",
    ")\n",
    "\n",
    "sampled_data = next(iter(val_loader))\n",
    "\n",
    "print(\"Sampled mini-batch:\")\n",
    "print(\"===================\")\n",
    "print(sampled_data)\n",
    "\n",
    "assert sampled_data[\"user\", \"rates\", \"movie\"].edge_label_index.size(1) == 3 * 128\n",
    "assert sampled_data[\"user\", \"rates\", \"movie\"].edge_label.min() >= 0\n",
    "assert sampled_data[\"user\", \"rates\", \"movie\"].edge_label.max() <= 1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "Vi25Z7lFPPjc",
    "outputId": "9cdf67ca-f335-4009-b2df-769697f1c99a"
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████████████████████████████████████████████████████████████████████████████| 79/79 [00:03<00:00, 20.29it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Validation AUC: 0.9307\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "from sklearn.metrics import roc_auc_score\n",
    "\n",
    "preds = []\n",
    "ground_truths = []\n",
    "for sampled_data in tqdm.tqdm(val_loader):\n",
    "    with torch.no_grad():\n",
    "        sampled_data.to(device)\n",
    "        preds.append(model(sampled_data))\n",
    "        ground_truths.append(sampled_data[\"user\", \"rates\", \"movie\"].edge_label)\n",
    "\n",
    "pred = torch.cat(preds, dim=0).cpu().numpy()\n",
    "ground_truth = torch.cat(ground_truths, dim=0).cpu().numpy()\n",
    "auc = roc_auc_score(ground_truth, pred)\n",
    "print(f\"Validation AUC: {auc:.4f}\")"
   ]
  }
 ],
 "metadata": {
  "colab": {
   "provenance": []
  },
  "kernelspec": {
   "display_name": "GNN",
   "language": "python",
   "name": "gnn"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.18"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
